from panda3d.core import Vec2,Point3,Vec3,Vec4
import math
from pandac.PandaModules import TextureStage
from direct.interval.IntervalGlobal import *
import copy
from item import *
from block import AirBlock
'''
	Makes the actual Mario character
'''		
class Mario:
	SPEED = 200
	JUMP_SPEED = 400
	
	'''
		Constructor for the Mario class
		Starts the game loop task (mario's walk class)
	'''
	def __init__(self, parent):
		self.parent = parent
		self.height = self.parent.BLOCK_SIZE
		self.width = self.parent.BLOCK_SIZE
		self.alive = True
		self.immune = False
		self.immuneTimer = 0
		
		self.loadModel()
		self.setupInterrupts()
		self.velocity = Vec2(0, 0)
		self.jumping = False
		self.JUMP_HEIGHT = self.parent.BLOCK_SIZE*4
		self.curJumpHeight = self.parent.BLOCK_SIZE*4	
		self.dieTime = 0
	'''
		Loads the Mario Model
	'''
	def loadModel(self):
		self.loadTexture()
		self.set2DProps()
		
	def startWalkingTask(self):
		self.prevTime = 0
		if (self.height > self.parent.BLOCK_SIZE):
			self.model.setY(self.model.getY() + self.parent.BLOCK_SIZE*0.25)
		self.walkingTask = self.parent.taskMgr.add(self.move, "mario_move")
		self.walkingTask.fps = 4
		self.velocity = Vec2(0, 0)
		self.jumping = False
		self.win = False
		self.alive = True
		self.setupInterrupts();
	'''
		Creates the plane and the texture
		
		Overwrite this in the subclasses of MarioActor to load correct model
	'''
	def loadTexture(self):
		self.model = self.parent.loader.loadModelCopy("models/plane")
		self.walkTextures = self.loadTextureMovie(2, 'models/characters/mario/mario_right', 'png')
		self.bigWalkTextures = self.loadTextureMovie(3, 'models/characters/mario/mario_big_right', 'png')
		self.jumpTexture = self.parent.loader.loadTexture("models/characters/mario/mario_jump_right.png")
		self.bigJumpTexture = self.parent.loader.loadTexture("models/characters/mario/mario_big_right_jump.png")
		self.dieTexture = self.parent.loader.loadTexture("models/characters/mario/mario_dead.png")
		
	'''
		Sets the properties for a two dimensional figure
	'''
	def set2DProps(self):
		self.model.setHpr(Vec3(0,-90,0))
		self.model.setScale(Vec3(self.width, self.parent.BLOCK_SIZE, self.height))
		
		# maximize efficiency
		self.model.setBin("unsorted", 0)
		self.model.setDepthTest(False)
		self.model.setTransparency(1)
		
		#self.model.setTexture(self.texture)
		self.model.reparentTo(render)
		
	'''
		Our custom load function to load the textures needed for a movie into a
		list. It assumes the the files are named
		"path/name<serial number>.extention"
		
		Params: frames -- The number of frames to load
				name -- The "path/name" part of the filename path
				suffix -- The "extention" part of the path
				padding -- The number of digit the serial number contians:
					e.g. if the serial number is 0001 then padding is 4
	'''
	def loadTextureMovie(self, frames, name, suffix, padding = 1):
		return [loader.loadTexture((name+"_%0"+str(padding)+"d."+suffix) % i) for i in range(frames)]
	
	'''
		Starts the dying animation for this mario character
	'''
	def startDieAnimation(self):
		# disable user control
		self.disableInterrupts()
		self.alive = False
		# make him look like he is dying
		self.model.setTexture(self.dieTexture, 1)
		self.parent.themeMusic.stop()
		Sequence(
		Func(self.parent.deathSound.play),
		Wait(5),
		Func(self.parent.themeMusic.play)
		).start()
		
		deathSequence = Sequence(
			Func(self.model.setTexture, self.dieTexture, 1),
			self.model.posInterval(0.25, 
				Vec3(self.model.getX() - self.parent.BLOCK_SIZE*0.5, self.model.getY() + self.parent.BLOCK_SIZE*0.5, 0)
			),
			self.model.posInterval(0.75, 
				Vec3(self.model.getX(), -self.parent.BLOCK_SIZE, 0)
			)
		)
		
		
		
		deathSequence.start()
	
	'''
		Sets the velocity of mario to move right or left
	'''
	def startWalk(self, direction):
		scale = self.model.getTexScale(TextureStage.getDefault())
		self.model.setTexScale(TextureStage.getDefault(), math.fabs(scale[0])*direction, scale[1])
		self.velocity[0] = direction*self.SPEED
	
	'''
		Sets the velocity to 0 to stop walking
	'''
	def stopWalk(self, direction):
		if ((self.velocity[0] > 0) == (direction > 0)):
			self.velocity[0] = 0
	
	'''
	
	'''
	def startJump(self, jumpHeight=None):

		if (jumpHeight == None):
			self.curJumpHeight = self.JUMP_HEIGHT
		else:
			self.curJumpHeight = jumpHeight
		self.velocity[1] = self.JUMP_SPEED
		self.originalJumpY = self.model.getY()
		self.jumping = True
		self.parent.ignore("w")
		
	
	'''
		Runs the task to animate the sprite as walking
	'''
	def move(self, task):
		if(self.win):
			self.parent.gameStats.win()
			self.model.hide()
			#self.model.setY(1)
			if(self.parent.overworld.getCurrentLevel() == 'level_three'):
				self.parent.overworld.showYouWin()
			return task.done
		if (self.model.getY() < 0):
			self.alive = False
			self.parent.themeMusic.stop()
			Sequence(
			Func(self.parent.deathSound.play),
			Wait(5),
			Func(self.parent.themeMusic.play)
			).start()
			
			Sequence(Wait(2.75),Func(self.parent.gameStats.died)).start();
			#self.model.setY(1)
			return task.done
			
		if (self.alive):
			self.dieTime = 0
			#Here we calculate the current frame number by multiplying the current time
			#(in seconds) by the frames per second variable we set earlier
			self.currentFrame = int(task.time * task.fps)
			delta_t = task.time - self.prevTime
			self.prevTime = task.time
			if (self.immune):
				self.immuneTimer += delta_t
				
			if (self.immuneTimer >= 1):
				self.immuneTimer = 0
				self.immune = False
			# makes mario move
			tmp_vel = copy.deepcopy(self.velocity)
			if (self.model.getX() + self.width/2.0 >= self.parent.level.width and tmp_vel[0] > 0):
				tmp_vel[0] = 0
			elif (self.model.getX() - self.width/2.0 <= 0 and tmp_vel[0] < 0):
				tmp_vel[0] = 0
			
			self.model.setPos(self.model.getPos() + Vec3(tmp_vel[0]*delta_t, tmp_vel[1]*delta_t, 0))
			self.checkCollisions()
		
			# starts walking animation if he should be walking
			if (self.velocity[0] != 0 and (not self.jumping or self.velocity[1] is 0)):
				if (self.height <= self.parent.BLOCK_SIZE):
					self.model.setTexture(self.walkTextures[self.currentFrame % len(self.walkTextures)], 1)
				else:
					self.model.setTexture(self.bigWalkTextures[self.currentFrame % len(self.bigWalkTextures)], 1)
				self.jumping = False
			elif (self.velocity[0] == 0 and self.velocity[1] == 0):
				self.currentFrame = 0
				if (self.height <= self.parent.BLOCK_SIZE):
					self.model.setTexture(self.walkTextures[self.currentFrame], 1)
				else:
					self.model.setTexture(self.bigWalkTextures[self.currentFrame], 1)
				self.jumping = False
			if(self.velocity[1] == 0):
				self.jumping = False
			
			elif (self.velocity[1] != 0):
				# mario is jumping here
				# switches the texture to face left if needed
				if (self.velocity[0] < 0):
					scale = self.model.getTexScale(TextureStage.getDefault())
					self.model.setTexScale(TextureStage.getDefault(), math.fabs(scale[0])*-1, scale[1])
					
				# mario can only jump up 3.5 block before having to come back down
				if (self.jumping and self.model.getY() - self.originalJumpY > self.curJumpHeight and self.velocity[1] > 0):
					self.velocity[1] = self.velocity[1]*-1
	 
				self.currentFrame = 0
				if (self.jumping == True):
					if (self.height <= self.parent.BLOCK_SIZE):
						self.model.setTexture(self.jumpTexture, 1)
					else:
						self.model.setTexture(self.bigJumpTexture, 1)
						
			
			
			return task.cont
		else:
			return task.cont
	
	'''
		Set's up the controls for mario to move
	'''	
	def setupInterrupts(self):
		self.parent.accept("a", self.startWalk, [-1])
		self.parent.accept("d", self.startWalk, [1])
		self.parent.accept("a-up", self.stopWalk, [-1])
		self.parent.accept("d-up", self.stopWalk, [1])
		self.parent.accept("w", self.startJump)
		self.parent.accept("s", self.doWin)
		
		self.parent.accept("arrow_left", self.startWalk, [-1])
		self.parent.accept("arrow_right", self.startWalk, [1])
		self.parent.accept("arrow_left-up", self.stopWalk, [-1])
		self.parent.accept("arrow_right-up", self.stopWalk, [1])
		self.parent.accept("arrow_up", self.startJump)
		self.parent.accept("arrow_down", self.doWin)
		
	'''
	
	'''
	def disableInterrupts(self):
		self.parent.ignore("a")
		self.parent.ignore("d")
		self.parent.ignore("a-up")
		self.parent.ignore("d-up")
		self.parent.ignore("w")
		self.parent.ignore("arrow_down");
		self.parent.ignore("s");
		
		self.parent.ignore("arrow_left")
		self.parent.ignore("arrow_right")
		self.parent.ignore("arrow_left-up")
		self.parent.ignore("arrow_right-up")
		self.parent.ignore("arrow_up")
		
		
	'''
	    Check's whether mario collides with the blocks or enemies
	'''
	def checkCollisions(self):
		# check collisions with the bottom of mario
		(y, x) = self.convertPosToMapCell(0, -0.5*self.height)
		if (y < self.parent.level.height and x < self.parent.level.width and y < self.parent.level.height/self.parent.BLOCK_SIZE - 1):
			if (self.parent.level.map[y][x].block.type != 'AIR' and self.velocity[1] < 0):
				self.velocity[1] = 0
				self.model.setPos(self.model.getX(), (y + 1)*self.parent.BLOCK_SIZE + 0.5*self.height, 0)
				self.parent.accept("w", self.startJump)
				
			if (self.parent.level.map[y-1][x].block.type == 'AIR' and not self.jumping):
				self.velocity[1] = -self.JUMP_SPEED
			
			for enemy in self.parent.level.map[y][x].enemies:
					if (self.collidesWith(enemy) == 1):
						enemy.startDieAnimation()
						self.startJump(jumpHeight=self.parent.BLOCK_SIZE)
						break;
			
			for item in self.parent.level.map[y][x].items:
				if (item.type == 'COIN'):
					self.parent.gameStats.addCoin()
				if (item.type == 'MUSHROOM'):
					self.height = self.parent.BLOCK_SIZE*1.5
					self.model.setScale(Vec3(self.width, self.parent.BLOCK_SIZE, self.height))
					self.model.setTexture(self.bigWalkTextures[0])
				self.parent.level.map[y][x].items.remove(item) 
				item.destroy()

						
			# check collisions for the top of mario
			(y, x) = self.convertPosToMapCell(0, 0.5*self.height)
			if (y < self.parent.level.height/self.parent.BLOCK_SIZE - 1):
				if (self.parent.level.map[y][x].block.type != 'AIR' and self.velocity[1] > 0 and self.jumping):
					self.velocity[1] = -self.JUMP_SPEED
					
					self.parent.level.map[y][x].block.startCollisionAnimation()
					
					if (self.parent.level.map[y][x].block.type == 'BLOCK'):
						if (self.height > self.parent.BLOCK_SIZE):
							self.parent.level.map[y][x].block.destroy()
							self.parent.level.map[y][x].block = AirBlock(self.parent)
					
					if (self.parent.level.map[y][x].block.type == 'QUESTION'):
						if (self.parent.level.map[y][x].block.numItems > 0):
							if (self.parent.level.map[y][x].block.item == 'MUSHROOM'):
								new_mush = Mushroom(self.parent)
								new_mush.model.setPos(
									Vec3((x + 0.5)*self.parent.BLOCK_SIZE, (y + 1.5)*self.parent.BLOCK_SIZE,0)
								)
								self.parent.level.map[y+1][x].items.append(new_mush)
								self.parent.level.map[y][x].block.numItems -= 1
							elif (self.parent.level.map[y][x].block.item == 'COIN'):
								new_coin = Item(self.parent)
								new_coin.model.setPos(
									Vec3((x + 0.5)*self.parent.BLOCK_SIZE, (y + 1.5)*self.parent.BLOCK_SIZE,0)
								)
								self.parent.level.map[y+1][x].items.append(new_coin)
								self.parent.level.map[y][x].block.numItems -= 1
							
							if (self.parent.level.map[y][x].block.numItems <= 0):
								self.parent.level.map[y][x].block.resetTexture("models/levels/objects/unbreakable_block.png")
							
					
					for enemy in self.parent.level.map[y+1][x].enemies:
						enemy.startDieAnimation()
						
					for item in self.parent.level.map[y][x].items:
						if (item.type == 'COIN'):
							self.parent.gameStats.addCoin()
						if (item.type == 'MUSHROOM'):
							self.height = self.parent.BLOCK_SIZE*1.5
							self.model.setScale(Vec3(self.width, self.parent.BLOCK_SIZE, self.height))
							self.model.setTexture(self.bigWalkTextures[0])
						self.parent.level.map[y][x].items.remove(item)
						item.destroy()

			# check collisions on the right
			if (self.height > self.parent.BLOCK_SIZE):
				(y, x) = self.convertPosToMapCell(0.5*self.width, 0.5*self.height)
				if (y < self.parent.level.height/self.parent.BLOCK_SIZE - 1):
					self.checkCollisionsRight(y,x)
				
			(y, x) = self.convertPosToMapCell(0.5*self.width, 0)
			if (y < self.parent.level.height/self.parent.BLOCK_SIZE - 1):
				self.checkCollisionsRight(y,x)

	
			# check collisions on the left
			if (self.height > self.parent.BLOCK_SIZE):
				(y, x) = self.convertPosToMapCell(-0.5*self.width, 0.5*self.height)
				if (y < self.parent.level.height/self.parent.BLOCK_SIZE - 1):
					self.checkCollisionsLeft(y,x)
			
			(y, x) = self.convertPosToMapCell(-0.5*self.width, 0)
			if (y < self.parent.level.height/self.parent.BLOCK_SIZE - 1):
				self.checkCollisionsLeft(y,x)
					
	'''
		Takes the coordinates of the model and changes them to figure
		out which cell the model is in
	'''
	def convertPosToMapCell(self, offsetX, offsetY):
		pos = self.model.getPos()
		return (int((pos[1] + offsetY)/self.parent.BLOCK_SIZE), int((pos[0]+offsetX)/self.parent.BLOCK_SIZE))
	
	def doWin(self):
		(y,x) = self.convertPosToMapCell(0, 0)
		if(self.parent.level.map[y-1][x].block.type == 'PIPE'):
			self.win = True
		
	'''
		Checks to see if mario collides with an enemy
		
		Returns a -1 if collision on the left or the right
				a 1 if the collision occurs on mario's bottom
				a 0 if there is no collision
	'''	
	def collidesWith(self, enemy):
		if (enemy.alive):
			if (self.model.getY() - 0.5*self.height <= enemy.model.getY() + 0.5*self.parent.BLOCK_SIZE and self.velocity[1] < 0):
				return 1
			elif (self.model.getX() + 0.5*self.width >= enemy.model.getX() - 0.5*self.parent.BLOCK_SIZE):
				return -1
			elif (self.model.getX() - 0.5*self.width >= enemy.model.getX() + 0.5*self.parent.BLOCK_SIZE):
				return -1
		
		return 0
		
	'''
		Correctly handles any collisions on the rght side of mario
	'''
	def checkCollisionsRight(self, y, x):
		if (self.parent.level.map[y][x].block.type != 'AIR'):
			#self.velocity[0] = 0
			self.model.setPos((x - 0.5)*self.parent.BLOCK_SIZE, self.model.getY(), 0)
		elif (self.parent.level.map[y][x].block.type == 'AIR'):
			for enemy in self.parent.level.map[y][x].enemies:
				if (self.collidesWith(enemy) == -1 and self.immune == False):
					if (self.height <= self.parent.BLOCK_SIZE):
						self.startDieAnimation()
						
					else:
						self.height = self.parent.BLOCK_SIZE
						self.model.setY(self.model.getY() - 0.5*self.parent.BLOCK_SIZE)
						self.model.setScale(Vec3(self.width, self.parent.BLOCK_SIZE, self.height))
						self.model.setTexture(self.walkTextures[0])
						self.immune = True
					break;
					
		for item in self.parent.level.map[y][x].items:
			if (item.type == 'MUSHROOM'):
				self.height = self.parent.BLOCK_SIZE*1.5
				self.model.setScale(Vec3(self.width, self.parent.BLOCK_SIZE, self.height))
				self.model.setTexture(self.bigWalkTextures[0])
			elif (item.type == 'COIN'):
				self.parent.gameStats.addCoin()
			
			self.parent.level.map[y][x].items.remove(item)
			item.destroy()
		
	'''
		Correctly handles any collisions on the left side of mario
	'''
	def checkCollisionsLeft(self, y, x):
		if (self.parent.level.map[y][x].block.type != 'AIR'):
			#self.velocity[0] = 0
			self.model.setPos((x + 1.5)*self.parent.BLOCK_SIZE, self.model.getY(), 0)
		elif (self.parent.level.map[y][x].block.type == 'AIR'):
			for enemy in self.parent.level.map[y][x].enemies:
				if (self.collidesWith(enemy) == -1  and self.immune == False):
					if (self.height <= self.parent.BLOCK_SIZE):
						self.startDieAnimation()
					else:
						self.height = self.parent.BLOCK_SIZE
						self.model.setY(self.model.getY() - 0.5*self.parent.BLOCK_SIZE)
						self.model.setScale(Vec3(self.width, self.parent.BLOCK_SIZE, self.height))
						self.model.setTexture(self.walkTextures[0])
						self.immune = True
					break;
					
		for item in self.parent.level.map[y][x].items:
			if (item.type == 'MUSHROOM'):
				self.height = self.parent.BLOCK_SIZE*1.5
				self.model.setScale(Vec3(self.width, self.parent.BLOCK_SIZE, self.height))
				self.model.setTexture(self.bigWalkTextures[0])
			elif (item.type == 'COIN'):
				self.parent.gameStats.addCoin()
			self.parent.level.map[y][x].items.remove(item)
			item.destroy()
		